﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BookSleeve;
using RedisDemos.Extensions;

namespace RedisDemos.Scenarios
{
    public class Tagging : Scenario
    {
        protected override async Task Perform(RedisConnection conn)
        {
            var today = DateTime.Today;

            await UserPost(conn, "tank-1",
                           new PostModel { Tags = "Win8", Title = "Win8 awesome features", 
                                           Content = "Content about Win8 awesome features", PostedAt = today });

            await UserPost(conn, "tank-1",
                           new PostModel { Tags = "NoSql", Title = "NoSql's wonderful world", 
                                           Content = "Content about NoSql's wonderful world", PostedAt = today.AddDays(1) });

            await UserPost(conn, "tank-1",
                           new PostModel { Tags = "NoSql, Redis", Title = "Redis's type system", 
                                           Content = "Content Redis's type system", PostedAt = today.AddDays(2) });

            await FetchPostByTag(conn, "Win8");
            await FetchPostByTag(conn, "NoSql");
            await FetchPostByTag(conn, "NoSql, Redis");

            await FetchAndCachePostByTag(conn, "NoSql, Redis");
            await FetchAndCachePostByTag(conn, "NoSql, Redis");
        }

        private async Task UserPost(RedisConnection conn, String username, PostModel model)
        {
            // INCR key 
            var postId = await conn.Strings.Increment(Db, "posts:nextid");

            // SADD key member [member ...] 
            var addTagTasks = ExtractTags(model.Tags)
                                .Select(t => conn.Sets.Add(Db, String.Format("tags:{0}", t), postId.Dump()));

            // HMSET key field value [field value ...] 
            var allTasks = addTagTasks.Union(new[] {conn.Hashes.Set(Db, String.Format("post:{0}", postId), model.AsHash()),
                                                    conn.Lists.AddLast(Db, String.Format("user:{0}:posts", username), postId.Dump())});

            //parallel invoke commands and await completion
            await Task.WhenAll(allTasks);
        }

        private async Task FetchPostByTag(RedisConnection conn, String tags)
        {
            var splittedTags = ExtractTags(tags);
            Byte[][] postIds = null;

            if (splittedTags.Count() == 1)
                // SMEMBERS key 
                postIds = await conn.Sets.GetAll(Db, String.Format("tags:{0}", splittedTags.Single()));
            else
                // SINTER key [key ...] 
                postIds = await conn.Sets.Intersect(Db, splittedTags.Select(t => String.Format("tags:{0}", t)).ToArray());

            "Post tagged: {0}".LogInfo(tags);
            foreach (var pid in postIds)
            {
                // HGETALL key 
                var fields = await conn.Hashes.GetAll(Db, String.Format("post:{0}", pid.AsString()));
                var post = fields.AsObject<PostModel>();
                "\t{0}".LogInfo(post.Title);
            }
        }

        private async Task FetchAndCachePostByTag(RedisConnection conn, String tags)
        {            
            var splittedTags = ExtractTags(tags);

            // SMEMBERS key 
            var postIds = await conn.Sets.GetAll(Db, String.Format("cache:tags:{0}", tags));
            if (postIds == null || postIds.Length == 0)
            {
                "Not in cache => IntersectAndStore".LogInfo();

                // SINTERSTORE destination key [key ...] 
                await conn.Sets.IntersectAndStore(Db, String.Format("cache:tags:{0}", tags), splittedTags.Select(t => String.Format("tags:{0}", t)).ToArray());

                // SMEMBERS key 
                postIds = await conn.Sets.GetAll(Db, String.Format("cache:tags:{0}", tags));
            }
            else
                "From cache".LogInfo();

            "Post tagged: {0}".LogInfo(tags);
            foreach (var pid in postIds)
            {
                // HGETALL key 
                var fields = await conn.Hashes.GetAll(Db, String.Format("post:{0}", pid.AsString()));
                var post = fields.AsObject<PostModel>();
                "\t{0}".LogInfo(post.Title);
            }
        }

        private static IEnumerable<String> ExtractTags(String tags)
        {
            return tags.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
                       .Select(t => t.Trim().ToLowerInvariant());
        }
    }
}
